`timescale 1ns / 1ps
/*
 Copyright 2020 Sean Xiao, jxzsxsp@qq.com
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 
 http://www.apache.org/licenses/LICENSE-2.0
 
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

module yue_rv32i_core # (
    parameter [31:0] TMR_BASEADDR  = 32'h0200_0000,
    parameter [31:0] PLIC_BASEADDR = 32'h0c00_0000,
    parameter [31:0] ITCM_BASEADDR = 32'h8000_0000,
    parameter [31:0] DTCM_BASEADDR = 32'h9000_0000,
    parameter [31:0] UART_BASEADDR = 32'he000_0000,
    parameter [31:0] GPIO_BASEADDR = 32'hf000_0000
)
(
    input  sys_clk,
    
//====================================
    input  [ 31: 0 ] i_GPIO_dina,
    output [ 31: 0 ] o_GPIO_douta,
    output [ 31: 0 ] o_GPIO_ta,
    
    input  [ 31: 0 ] i_GPIO_dinb,
    output [ 31: 0 ] o_GPIO_doutb,
    output [ 31: 0 ] o_GPIO_tb,
    
    input  [ 31: 0 ] i_GPIO_dinc,
    output [ 31: 0 ] o_GPIO_doutc,
    output [ 31: 0 ] o_GPIO_tc,

    input  [ 31: 0 ] i_GPIO_dind,
    output [ 31: 0 ] o_GPIO_doutd,
    output [ 31: 0 ] o_GPIO_td,

    output           txd_start,
    output [7:0]     txd_data,
    input            txd_done,
    
//====================================
    output  [31:0]  o_sft_int_v,
    output  [31:0]  o_timer_l,
    output  [31:0]  o_timer_h,
            
    input   [31:0]  i_timer_l,
    input   [31:0]  i_timer_h, 
            
    output  [31:0]  o_tcmp_l,
    output  [31:0]  o_tcmp_h,
            
    output  [1:0]   o_timer_valid,
    output  [31:0]  o_tm_ctrl,
             
    
    input   [11:0]  code_addr,          
    input   [31:0]  code_din,
    input           code_wea,
    
//====================================
    input           i_ext_irq,
    input           i_sft_irq,
    input           i_tmr_irq,

    output          o_meie,
    output          o_msie,
    output          o_mtie,
    output          o_glb_irq,

//====================================
    input           i_cpu_reset,
    input           rst_n
);

//===============================================================================
localparam [ 2: 0 ] IDLE  = 8'd0,
                    I_FCH = 8'd1,
                    I_EXE = 8'd2,
                    I_LS  = 8'd3,
                    I_WB  = 8'd4,
                    I_RO1 = 8'd5,
                    I_RO2 = 8'd6,
                    I_RO3 = 8'd7;




wire ls_need;
wire wb_need;
wire ls_rdy;
wire wb_rdy;

// load program data
wire o_CPU_cs;
wire [ 31: 0 ] o_CPU_PC;



//===============================================================================
reg [ 2: 0 ] instr_st = 0;
reg [ 2: 0 ] instr_st_nxt = 0;
always@( posedge sys_clk )
if (( rst_n == 1'b0 ) | i_cpu_reset )     instr_st <= IDLE;
else    instr_st <= instr_st_nxt;



always @ ( * )
begin
    case ( instr_st )
    IDLE:   // 0
    begin
        if(!i_cpu_reset)
            instr_st_nxt = I_FCH;
        else
            instr_st_nxt = IDLE;
    end
    I_FCH:   // 1
    begin
        if(i_cpu_reset) instr_st_nxt = IDLE;
        else instr_st_nxt = I_EXE;
    end
    I_EXE:   // 2
    begin
        if ( ls_need )
        begin
            if ( o_CPU_cs )
                instr_st_nxt = I_RO1;
            else
                instr_st_nxt = I_LS;
        end
        else if ( wb_need )
            instr_st_nxt = I_WB;
        else 
            instr_st_nxt = I_WB;
    end
    I_LS:   // 3
    begin
        if ( ls_rdy )
        begin
            if ( wb_need )
                instr_st_nxt = I_WB;
            else
                instr_st_nxt = I_FCH;
        end
        else 
            instr_st_nxt = I_LS;
    end
    I_WB:   // 4
    begin
        if ( wb_rdy )
            instr_st_nxt = I_FCH;
        else 
            instr_st_nxt = I_WB;
    end
    I_RO1:   // 5
    begin
        instr_st_nxt = I_RO2;
    end
    I_RO2:   // 6
    begin
        instr_st_nxt = I_FCH;
//        instr_st_nxt = I_EXE;
    end
    I_RO3:   // 7
    begin
        instr_st_nxt = I_FCH;
    end
    default : instr_st_nxt = IDLE;
    endcase
end
//===============================================================================
/*
reg [2:0] pc_load_r = 0;
always @ (posedge sys_clk or negedge rst_n)
if(~rst_n) pc_load_r <= 0;
else pc_load_r <= {pc_load_r[1:0], o_CPU_cs};
*/

reg            irq_tch_r = 0;
always @ (posedge sys_clk or negedge rst_n)
if(~rst_n) irq_tch_r <= 0;
else if(instr_st == I_FCH)  irq_tch_r <= (i_ext_irq | i_sft_irq | i_tmr_irq);


//wire w_irq_src = ({pc_load_r, o_CPU_cs} != 0) ? 1'b0 : irq_tch_r;
wire w_irq_src = o_CPU_cs ? 1'b0 : irq_tch_r;
wire jump_irq_pc = w_irq_src & o_glb_irq;
wire            w_exp_src = 0;
wire irq_exp = w_irq_src | w_exp_src ;

wire            mret;
wire    [31:0]  mepc;
wire    [31:0]  w_irq_pc;
reg [ 31: 0 ] i_fch_PC = 0;
reg [ 31: 0 ] res_PC = 0;
wire [31:0] w_exe_PC;  

always @( posedge sys_clk or negedge rst_n )
if ( !rst_n ) i_fch_PC <= ITCM_BASEADDR;
else 
begin
    if ( instr_st == IDLE )         //0
        i_fch_PC <= ITCM_BASEADDR;
    else if ( instr_st == I_EXE )   //2
    begin
        if( mret )
            i_fch_PC <= mepc;
        else if (jump_irq_pc)
            i_fch_PC <= w_irq_pc;
        else if( o_CPU_cs )
        begin
            i_fch_PC <= o_CPU_PC;
            res_PC <= w_exe_PC;
        end
        else i_fch_PC <= w_exe_PC;
    end
    else if ( instr_st == I_RO1 )
    begin
        i_fch_PC <= res_PC;
    end
end

//===============================================================================
reg EXE_vld = 0;
always @( posedge sys_clk or negedge rst_n )
if ( !rst_n ) EXE_vld <= 0;
else
begin
    if ( instr_st == I_FCH )        //1
        EXE_vld <= 1'b1;
    else
        EXE_vld <= 1'b0;
end


wire [ 31: 0 ] instr;

reg [ 31: 0 ] exe_instr = 0;
always @( posedge sys_clk or negedge rst_n )
if ( !rst_n ) exe_instr <= 0;
else if ( instr_st == I_FCH )
    exe_instr <= instr;



reg i_CPU_load_vld = 0;
reg [ 31: 0 ] i_CPU_load_data = 0;
always @( posedge sys_clk or negedge rst_n )
if ( !rst_n ) 
begin
    i_CPU_load_data <= 0;
    i_CPU_load_vld <= 0;
end
else if ( instr_st == I_RO2 )
begin
    i_CPU_load_data <= instr;
    i_CPU_load_vld <= 1'b1;
end
else  i_CPU_load_vld <= 1'b0;

//===============================================================================

wire [ 31: 0 ] instr_PC = i_fch_PC;

rv32I_exu  #
(
    .TMR_BASEADDR   ( TMR_BASEADDR ),
    .PLIC_BASEADDR  ( PLIC_BASEADDR ),
    .CPU_BASEADDR   ( ITCM_BASEADDR ),
    .MEM_BASEADDR   ( DTCM_BASEADDR ),
    .UART_BASEADDR  ( UART_BASEADDR ),
    .GPIO_BASEADDR  ( GPIO_BASEADDR )
)
rv32I_exu_inst
(
    .sys_clk        ( sys_clk ),
    
    .i_ir           ( exe_instr ),  // The instruction register
    .i_PC           ( instr_PC ),   // The PC register along with
    .i_EXE_vld      ( EXE_vld ),
    
    .i_CPU_load_vld ( i_CPU_load_vld),
    .i_CPU_load_data( i_CPU_load_data ),
    
    .o_ls_need      ( ls_need ),
    .o_wb_need      ( wb_need ),
    .o_wb_rdy       ( wb_rdy ),
    .o_ls_rdy       ( ls_rdy ),
    
    .o_exe_PC       ( w_exe_PC ),

    // load program data
    .o_CPU_cs       ( o_CPU_cs ),
    .o_CPU_PC       ( o_CPU_PC ),
    
    .i_ext_irq      ( i_ext_irq ),
    .i_sft_irq      ( i_sft_irq ),
    .i_tmr_irq      ( i_tmr_irq ),
    
    .o_meie         ( o_meie ),
    .o_msie         ( o_msie ),
    .o_mtie         ( o_mtie ),
    .o_glb_irq      ( o_glb_irq ),

    .i_irq_src      ( w_irq_src ),
    .i_exp_src      ( w_exp_src ),

    .o_mret         ( mret ),
    .o_irq_pc       ( w_irq_pc ),
    .o_mepc         ( mepc ),
    
    .i_GPIO_dina    ( i_GPIO_dina ),
    .o_GPIO_douta   ( o_GPIO_douta ),
    .o_GPIO_ta      ( o_GPIO_ta ),
    
    .i_GPIO_dinb    ( i_GPIO_dinb ),
    .o_GPIO_doutb   ( o_GPIO_doutb ),
    .o_GPIO_tb      ( o_GPIO_tb ),
    
    .i_GPIO_dinc    ( i_GPIO_dinc ),
    .o_GPIO_doutc   ( o_GPIO_doutc ),
    .o_GPIO_tc      ( o_GPIO_tc ),

    .i_GPIO_dind    ( i_GPIO_dind ),
    .o_GPIO_doutd   ( o_GPIO_doutd ),
    .o_GPIO_td      ( o_GPIO_td ),
    
    .txd_start      ( txd_start ),
    .txd_data       ( txd_data ),
    .txd_done       ( txd_done ),
    
    .o_sft_int_v    ( o_sft_int_v ),
    .i_timer_l      ( i_timer_l ),
    .i_timer_h      ( i_timer_h ),
                    
    .o_timer_l      ( o_timer_l ),
    .o_timer_h      ( o_timer_h ),
                    
    .o_tcmp_l       ( o_tcmp_l ),
    .o_tcmp_h       ( o_tcmp_h ),
                   
    .o_timer_valid  ( o_timer_valid ),
    .o_tm_ctrl      ( o_tm_ctrl),
    
    .i_cpu_reset    ( i_cpu_reset),
    .rst_n          ( rst_n )
);
//===============================================================================



//===============================================================================

wire instr_ena = ( instr_PC[ 31: 16 ] == ITCM_BASEADDR[31:16] ) ? 1'b1 : 1'b0;

TDP_RAM_INSTR  program_inst
(
    .clock_a   ( sys_clk ),
    .rden_a    ( 1'b1 ),
    .wren_a    ( code_wea ),
    .address_a  ( code_addr ),
    .data_a   ( code_din ),
    .q_a  ( ),

    .clock_b   ( sys_clk ),
    .rden_b    ( instr_ena ),
    .wren_b    ( 1'b0 ),
    .address_b  ( instr_PC[ 13: 2 ] ), //8K 32bits, 32K byte
    .data_b   ( 32'b0 ),
    .q_b  ( instr )
);
//===============================================================================


endmodule
